/**
  * @file: Terrain.cpp
  * @author: Adrian Johnston and Steven Hobson-Campbell
  */

#include "../include/Terrain.h"
#include "../include/Functions.h"
#include "../include/Structures.h"
#define BUFFER_OFFSET(x) ((char*)NULL+(x))

Terrain::Terrain(std::string hmFilename, GLuint shaderProg)
    : xScale(1),
      yScale(1),
      zScale(1),
      filename(hmFilename),
      imageLoaded(false),
      width(0),
      height(0),
      shaderProg(shaderProg)
{
    load();
}

Terrain::~Terrain(){
    delete[] vertices;
    delete[] indices;
}

/*
 *	Loads the Terrain Map into a VBO ready for rendering
 */
void Terrain::load(){
    //Load Terrain Height Map from file
    if(!heightMap.loadFromFile(filename)){
        exit(-1);
    }
    /* Used for Grass Cluster
    if(!splatMap.loadFromFile("resources/terrain/splatmap.jpg")){
        std::cout << "Error Loading SplatMap for Grass Creation" << std::endl;
        exit(-1);
    }*/
	//Load textures
	splatID = CG::loadTexture("resources/terrain/splatmap.jpg");
	dirtID = CG::loadTexture("resources/images/dirt.jpg");
	grassID = CG::loadTexture("resources/images/grass.jpg");
    sandID = CG::loadTexture("resources/images/sand.jpg");
	rockID = CG::loadTexture("resources/images/rock.jpg");

    imageLoaded = true;

    /*Set the width and height of the terrain*/
    width = heightMap.getSize().x;
    height = heightMap.getSize().y;

    /*Allocate the arrays for the terrain and the noramls*/
    numVertices = width*height;
    int numTriangles = 2*(numVertices-width-height+1);
    vertices = new Vertex[numVertices];
    noIndices = 3*numTriangles;
    indices = new GLuint[noIndices];
    normalsByPosition = new std::list<Normal*>[width*height];

    terrainHeight = new GLfloat[width*height];
    for(int z = 0 ; z < height; z++){
        for (int x = 0;x < width; x++) {
            // compute the height as a value between 0.0 and 1.0
			GLfloat pointHeight = (float)(heightMap.getPixel(x,z)).r /(255*2);
			pointHeight += (float)(heightMap.getPixel(x,z)).g /(255*2);
			pointHeight -= (float)(heightMap.getPixel(x,z)).b /(255*2);
            vertices[x + z*width].position[0] = x;
            vertices[x + z*width].position[1] = pointHeight;
            vertices[x + z*width].position[2] = ((float)z)-height; //Working into -Z direction
            terrainHeight[x + z*width] = pointHeight;
        }
    }

    //Calculate the indices for each face of the mesh
    for(int i = 0, v = 0; i < (3*numTriangles) && numTriangles >= 2; i += 6) {
        indices[i] = v;
        indices[i+1] = v+1+width;
        indices[i+2] = v+1;
        indices[i+3] = v;
        indices[i+4] = v+width;
        indices[i+5] = v+width+1;
        if((v+1)%width == (width-1)) {
            v += 2;
        } else {
            v++;
        }
    }

    //Splat coord positions
    for(int x = 0; x < width; x++) {
        for(int z = 0; z < height; z++) {
            vertices[x+(z*width)].texCoords[0] = (1.0/width)*x;
            vertices[x+(z*width)].texCoords[1] = (1.0/height)*z;
        }
    }

    //Calculate face normals
    for(int z = 0; z < height-1; z++) {
        for(int x = 0; x < width-1; x++) {
            float vec1[3];
            float vec2[3];
            float cross[3];
            vec1[0] = vertices[x+1+((z+1)*width)].position[0] - vertices[x+(z*width)].position[0];
            vec1[1] = vertices[x+1+((z+1)*width)].position[1] - vertices[x+(z*width)].position[1];
            vec1[2] = vertices[x+1+((z+1)*width)].position[2] - vertices[x+(z*width)].position[2];
            vec2[0] = vertices[x+1+(z*width)].position[0] - vertices[x+(z*width)].position[0];
            vec2[1] = vertices[x+1+(z*width)].position[1] - vertices[x+(z*width)].position[1];
            vec2[2] = vertices[x+1+(z*width)].position[2] - vertices[x+(z*width)].position[2];
            CG::cross(vec1, vec2, cross);
            Normal *n = new Normal;
            n->components[0] = cross[0];
            n->components[1] = cross[1];
            n->components[2] = cross[2];
            normalsByPosition[x+(z*width)].push_back(n);
            normalsByPosition[x+1+((z+1)*width)].push_back(n);
            normalsByPosition[x+1+(z*width)].push_back(n);
            vec2[0] = vertices[x+((z+1)*width)].position[0] - vertices[x+(z*width)].position[0];
            vec2[1] = vertices[x+((z+1)*width)].position[1] - vertices[x+(z*width)].position[1];
            vec2[2] = vertices[x+((z+1)*width)].position[2] - vertices[x+(z*width)].position[2];
            CG::cross(vec2, vec1, cross);
            n = new Normal;
            n->components[0] = cross[0];
            n->components[1] = cross[1];
            n->components[2] = cross[2];
            normalsByPosition[x+(z*width)].push_back(n);
            normalsByPosition[x+((z+1)*width)].push_back(n);
            normalsByPosition[x+1+((z+1)*width)].push_back(n);
        }
    }

    //Average normals
    for(int x = 0; x < width; x++) {
        for(int z = 0; z < height; z++) {
            int normCount = 0;
            GLfloat normal[3] = {0, 0, 0};
            std::list<Normal*>::iterator it;
            std::list<Normal*> *currList = normalsByPosition+(x+(z*width));
            for(it = currList->begin(); it != currList->end(); it++) {
                normCount++;
                normal[0] += (*it)->components[0];
                normal[1] += (*it)->components[1];
                normal[2] += (*it)->components[2];
            }
            normal[0] /= normCount;
            normal[1] /= normCount;
            normal[2] /= normCount;
            vertices[x+(z*width)].normal[0] = normal[0];
            vertices[x+(z*width)].normal[1] = normal[1];
            vertices[x+(z*width)].normal[2] = normal[2];
        }
    }

    /*
    //Free up temporary normal data
    for(int x = 0; x < width; x++) {
        for(int z = 0; z < height; z++) {
            std::list<Normal*> *currList = normalsByPosition+(x+(z*width));
            while(!currList->empty()) {
                delete currList->front();
                currList->pop_front();
            }
            //for(it = currList->begin(); it != currList->end(); it++) {
                //delete (*it);
            //}
            //currList->clear();
        }
    }
    delete[] normalsByPosition;
    */

    //We now have an array of Vertices
    //Send vertex data to GPU
    glGenBuffers(1, &vboID);
    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*numVertices, NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex)*numVertices, vertices);
    //Send index data to GPU
    glGenBuffers(1, &indicesVBOID);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVBOID);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*noIndices, NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(GLuint)*noIndices, indices);

    /*Add grass clusters to the world*/

    //grass = new Grass("resources/images/grass_object.jpg", "resources/images/grass_object_alpha.jpg");
    //vec3 *pos;
    //pos->x = 290;
    //pos->y = 30;
    //pos->z = -290;
    //std::cout << "Here" << std::endl;
   // grassCluster = new GrassCluster(290, 30, -290, 20, 10, grass);
   // grassList.push_back(grassCluster);

    /*
    for(int z = 0 ; z < height; z++*radius){
        for (int x = 0 ;x < width; x++*radius) {
            // compute the height as a value between 0.0 and 1.0
            positions = new vec3[(int)density];
            GLfloat red = splatMap.getPixel(x,z).r;
            GLfloat green = splatMap.getPixel(x,z).g;
            GLfloat blue = splatMap.getPixel(x,z).b;

            //If not sand
            if(red == 0){
                for(int i = 0; i < density; i++) {
                    positions[i].x = x + ((float)(rand()%50)/50)*radius;
                    positions[i].z = z + ((float)(rand()%50)/50)*radius;
                    positions[i].y =
                }
                //New grass cluster based on x,y,z of heightmap
                grassList.push_back((GrassCluster *)new GrassCluster(x,vertices[x+z*width].position[1],-z,radius,density,grass,positions));
                std::cout << "Drawing grass at: " << positions->x << " " << positions->y << " " << positions->z << std::endl;
            }
            delete[] positions;
        }
    }*/

}

void Terrain::setScale(GLfloat x, GLfloat y, GLfloat z) {
    xScale = x;
    yScale = y;
    zScale = z;
}

void Terrain::render(vec3 camPos){


    glUseProgram(shaderProg);

    //If the Image is not loaded. Load it
    if(!imageLoaded)
        load();

    GLuint loc = glGetUniformLocation(shaderProg, "stepSize");
    glUniform1f(loc, 1.0/width);

    //Setup texture units
    CG::setTextureUnit(shaderProg, "splatTex", 0);
	CG::setTextureUnit(shaderProg, "dirtTex", 1);
	CG::setTextureUnit(shaderProg, "grassTex", 2);
	CG::setTextureUnit(shaderProg, "sandTex", 3);
	CG::setTextureUnit(shaderProg, "rockTex", 4);

    glEnable(GL_TEXTURE_2D);
    CG::bindTexture(splatID, GL_TEXTURE0);
	CG::bindTexture(dirtID, GL_TEXTURE1);
	CG::bindTexture(grassID, GL_TEXTURE2);
	CG::bindTexture(sandID, GL_TEXTURE3);
	CG::bindTexture(rockID, GL_TEXTURE4);

    glBindBuffer(GL_ARRAY_BUFFER, vboID);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesVBOID);
    //Send splat coords
    glClientActiveTexture(GL_TEXTURE0);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(24));

    glPushMatrix();
    glScalef(xScale, yScale, zScale);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);
    glNormalPointer(GL_FLOAT, sizeof(Vertex),  BUFFER_OFFSET(12));
    glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));
    glDrawElements(GL_TRIANGLES, noIndices, GL_UNSIGNED_INT, BUFFER_OFFSET(0));
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

    //Disable texture 0
    glActiveTexture(GL_TEXTURE0);
    glClientActiveTexture(GL_TEXTURE0);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisable(GL_TEXTURE_2D);
    glPopMatrix();


   // glPushMatrix();
   // if(grassList.size() > 0)
        //((GrassCluster*)(grassList.at(0)))->render(camPos);
    //glPopMatrix();
}

std::string Terrain::getHMFileName(){
    return filename;
}

GLfloat *Terrain::getHeightmap() {
    return terrainHeight;
}

GLuint Terrain::getWidth() {
    return width;
}

GLuint Terrain::getHeight() {
    return height;
}

vec3 Terrain::getScale() {
    vec3 scale;
    scale.x = xScale;
    scale.y = yScale;
    scale.z = zScale;
    return scale;
}
